Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP]: Shadow dom rfc #7687

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft

[WIP]: Shadow dom rfc #7687

wants to merge 4 commits into from

Conversation

snowystinger
Copy link
Member

Closes

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

🧢 Your Project:

@snowystinger snowystinger changed the title Shadow dom rfc [WIP]: Shadow dom rfc Jan 30, 2025
@rspbot
Copy link

rspbot commented Jan 30, 2025


## Motivation

As Web Components have become more prominent and more libraries and applications use them, we have encountered friction for people trying to adopt or use our libraries alongside others.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use case is beyond web components. For example, 3rd party widgets or SDKs who do not want their styles to be affected by user's website code may want to use shadow DOM without web components. This is what we do. The other option for such apps is dynamic iframes. The good thing is that with the changes we make for shadow DOM, we automatically take care of dynamic iframes too by using the correct document.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I'll reframe a bit, I was using Web Components as a bit of a stand-in term for shadow dom, but this is more accurate

I think we are potentially over simplifying by stating that solving shadow dom will solve iframes. They are two fundamentally different technologies even if they have overlap in terms of problems they solve.

See for example the need for


This is not needed for Shadow DOM. If we only worked on supporting Shadow DOM, this would be missed. That is because events inside an iframe will not appear outside the frame. While for Shadow DOM, events still have a capture phase and bubble phase outside of the Shadow root.

There will be other cases where behaviour differs between the two technologies.


As mentioned earlier, there are proposed parts to this initiative:

1. Custom React Testing Library Renderer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not write special tests for shadow DOM. All our component testing is done using playwright's component testing. We run the same tests both with and without shadow DOM.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what this comment has to do with us? The RFC is for what we need to do to support shadow dom in our library.

I'm also suggesting that we run the same tests both with and without shadow DOM

we should create a custom React Testing Library renderer for our unit tests which can wrap each test's rendered dom in a shadow root.
... without needing to write many specific tests.

There will need to be specific tests as well, but they can be added as edge cases are discovered. For now I'd like to get all of our current tests running both ways.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what this comment has to do with us?

Not much. This was just me sharing how we handle this situation.


In addition, contributions may occur for a little while until those teams have their needs met. However, it should be assumed that there will be further work to complete the goals as outlined here.

Another concern is that the current approach is, for lack of a better word, hacky. This is because we are accessing and manipulating the Shadow DOM in ways that it wasn't really intended. If we were to rewrite our library today, there are other ways we'd solve these issues which would respect more of the concept of the Shadow DOM and its purpose. What we do here and now may complicate a future where we have different APIs to support this vision of what support would ideally look like.
Copy link
Contributor

@ritz078 ritz078 Feb 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think one way to think about this is supporting different root elements instead of just think about it as shadow DOM support. See #7743 for example. Components like modal and popover always treat document.body as their root element. Instead react-aria should provide options to treat any other element as a container element.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly, what happens in the case of nested shadow doms though? Would we expect people to addWindowFocusTracking equivalent for each root and each hook/function that needs it? Would components need to accept a root element to pass down to hooks? context?

This wouldn't work for closed roots. This is why we want to re-think some of the ways parts of our library work. For example, FocusScope moves focus around for you when it's contain=true. This can't move into closed shadow roots, which is why the video tag breaks our FocusScope at the moment in certain cases. Instead, if we moved to a sentinel-based approach, we could let the browser handle the tab movement.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially I thought that supporting closed shadow root might not be a high priority at the beginning but you raised a good point about the video element. In that case, we do need to support both modes of shadow root.

Would we expect people to addWindowFocusTracking equivalent for each root and each hook/function that needs it? Would components need to accept a root element to pass down to hooks? context?

This might not be a good solution in the long run. But I can't answer much right now before going into the details of closed shadow root.


## Open Questions

* How to actually define the limitations of our support? See Introduction, it's missing a final sentence with this information.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the best way to approach it is to come up with a setup where all new code is compatible and the existing code can be changed when someone needs it. I think in a while, it will be just about using a set of utilities.

We can start with the getOwnerWindow and getOwnerDocument eslint rule in more packages.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is ambiguous

all new code is compatible

Does that mean it works in an application that has a single shadow dom at the root? that it can use video anywhere? that it can have 3rd party web components anywhere? that it can have shadow roots anywhere?

What if the new code is a new feature in an existing component or hook that has no support? what if that hook depends on lower hooks that don't have support and changing those hooks would also change how other components work.

It's a lofty goal and I think we should aim for support of everything. But it has to be moderated with the cost of implementation and ongoing support.


I don't believe this is true

it will be just about using a set of utilities

as I mentioned earlier, in order to support closed root (as an example), we have to rethink how many components and hooks are implemented.


I agree with this

We can start with the getOwnerWindow and getOwnerDocument eslint rule in more packages.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. If the aim here is to add full support for shadow DOM (which will be great), this isn't as simple as adding utilities. When I say full support, I mean everything just works out of the box irrespective of the where a shadow DOM is located. And that will include much more than some utilities and will involve refactoring of certain sections.


## Help Needed

The biggest help we can receive is tests, either in the form of unit tests or in the form of examples of real life applications/setups that we can turn into unit tests. The more tests we have, the less likely we will break anything moving forward after the initial effort is complete.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to avoid maintaining a fork so we will try to do our best here. Right now we use patches which are a nightmare during upgrades.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what this has to do with the "help needed" section? Are you just saying it will be hard to create tests and examples?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for not being clear. I meant we will like to help here as much as possible so that we do not have to maintain a fork. At the end of the day, we want shadow DOM support to land up here.


## Open Questions

* How to actually define the limitations of our support? See Introduction, it's missing a final sentence with this information.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A possible classification:
Container components that can have anything vs create name components such as RadioGroup that don't allow shadowdom's inside them


## Motivation

As Web Components have become more prominent and more libraries and applications use them, we have encountered friction for people trying to adopt or use our libraries alongside others.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I'll reframe a bit, I was using Web Components as a bit of a stand-in term for shadow dom, but this is more accurate

I think we are potentially over simplifying by stating that solving shadow dom will solve iframes. They are two fundamentally different technologies even if they have overlap in terms of problems they solve.

See for example the need for


This is not needed for Shadow DOM. If we only worked on supporting Shadow DOM, this would be missed. That is because events inside an iframe will not appear outside the frame. While for Shadow DOM, events still have a capture phase and bubble phase outside of the Shadow root.

There will be other cases where behaviour differs between the two technologies.


As mentioned earlier, there are proposed parts to this initiative:

1. Custom React Testing Library Renderer
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what this comment has to do with us? The RFC is for what we need to do to support shadow dom in our library.

I'm also suggesting that we run the same tests both with and without shadow DOM

we should create a custom React Testing Library renderer for our unit tests which can wrap each test's rendered dom in a shadow root.
... without needing to write many specific tests.

There will need to be specific tests as well, but they can be added as edge cases are discovered. For now I'd like to get all of our current tests running both ways.


In addition, contributions may occur for a little while until those teams have their needs met. However, it should be assumed that there will be further work to complete the goals as outlined here.

Another concern is that the current approach is, for lack of a better word, hacky. This is because we are accessing and manipulating the Shadow DOM in ways that it wasn't really intended. If we were to rewrite our library today, there are other ways we'd solve these issues which would respect more of the concept of the Shadow DOM and its purpose. What we do here and now may complicate a future where we have different APIs to support this vision of what support would ideally look like.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly, what happens in the case of nested shadow doms though? Would we expect people to addWindowFocusTracking equivalent for each root and each hook/function that needs it? Would components need to accept a root element to pass down to hooks? context?

This wouldn't work for closed roots. This is why we want to re-think some of the ways parts of our library work. For example, FocusScope moves focus around for you when it's contain=true. This can't move into closed shadow roots, which is why the video tag breaks our FocusScope at the moment in certain cases. Instead, if we moved to a sentinel-based approach, we could let the browser handle the tab movement.


## Open Questions

* How to actually define the limitations of our support? See Introduction, it's missing a final sentence with this information.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is ambiguous

all new code is compatible

Does that mean it works in an application that has a single shadow dom at the root? that it can use video anywhere? that it can have 3rd party web components anywhere? that it can have shadow roots anywhere?

What if the new code is a new feature in an existing component or hook that has no support? what if that hook depends on lower hooks that don't have support and changing those hooks would also change how other components work.

It's a lofty goal and I think we should aim for support of everything. But it has to be moderated with the cost of implementation and ongoing support.


I don't believe this is true

it will be just about using a set of utilities

as I mentioned earlier, in order to support closed root (as an example), we have to rethink how many components and hooks are implemented.


I agree with this

We can start with the getOwnerWindow and getOwnerDocument eslint rule in more packages.

@rspbot
Copy link

rspbot commented Feb 10, 2025

@rspbot
Copy link

rspbot commented Feb 10, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants